<?php


class QUrlManager extends QObject 
{
	/**
	 * Id the generated path gets too long the web server will throw an error
	 * In order to avoid this we insert '/-/' when the length is more then $MaxGeneratedPathChunkSize
	 * The '/-/' is removed on decode
	 *
	 * @var integer
	 */
	public static $MaxGeneratedPathChunkSize = 200;
	/**
	 * The events are grouped in a tree in order to determine how they will be placed in the URL 
	 * Events with the same group will overwrite the old event and take it's place in the URL
	 * For example: if we have two buttons in the main menu (Products & Services) , they should both 
	 * have the same group and the same position in the URL and can not be both at the same time
	 * Further more, child groups will be removed if a parrent group event gets triggered 
	 * For example , if we click on `Products` we need to remove `the selected product` event
	 * and display all of them
	 *
	 * @var array
	 */
	protected $urlGroupsTree = array();

	protected $call_stack;
	protected $url_groups = array();

	protected $alias_table = array();
	protected $reverse_alias_table = array();
	
	protected $decode_parts = null;
	
	private $load_url_groups = null;
	
	private $update_hash = false;
	private $no_update_hash = false;

	public function __construct()
	{
		parent::__construct();
	}
	
	public function getUpdateHash()
	{
		return $this->update_hash;
	}
	
	public function load($call_stack)
	{
		$this->call_stack = $call_stack;
		
		// if we have a web event and not load by HASH we must load the prev events
		if (is_null($this->load_url_groups))
			$this->load_url_groups = isset($_POST["__wb_events"]);
		
		if (isset($_SESSION["___urls"]) && $this->load_url_groups)
			$this->url_groups = $_SESSION["___urls"];
	}

	public function unload()
	{
		$_SESSION["___urls"] = $this->url_groups;
	}
	
	public final function onTriggerEvent(QIWebListener $listener, $sender_id, QWebEvent $event, $parameters)
	{
		if (!$event->getUrlGroup())
			return;
			
		if (!$this->no_update_hash)
			$this->update_hash = true;
		
		// only keep parameters that differ from the default ones
		$minimal_params = $event->getMinimalParameters($parameters);

		$enc_ev = $this->encodeEvent($sender_id, $event->getType(), $minimal_params);
		if (is_string($enc_ev))
		{
			$d_len = strlen($enc_ev);
			if ($d_len > 0)
			{
				$last_ch = substr($enc_ev, $d_len - 1);
				if (($last_ch == "~") || ($last_ch == "_") || ($last_ch == "-"))
					$enc_ev = $enc_ev . "-";
			}
		}
		else 
			$enc_ev = $enc_ev[0];
		$this->url_groups[$event->getUrlGroup()] = $enc_ev;
		// must unset all child groups 
		$child_groups = self::GetChildUrlGroups($event->getUrlGroup());

		if ($child_groups)
			foreach ($child_groups as $grp)
			{
				if (isset($this->url_groups[$grp]) && $this->url_groups[$grp])
					$this->url_groups[$grp] = null;
			}
	}

	public function getUrlPrefix()
	{
		return "";
	}
	
	public function getLink($type, $params = null)
	{
		// throw exception, this method must be implemented
		throw new Exception("QUrlManager::getLink must be implemented in a child class");
	}
	
	public function getEventLink(QWebControl $control, QWebEvent $event, $params = null, $merge_params = false)
	{
		if (!$control->getWebPage()->hasTriggeredEvents())
			throw new Exception("can not get a link until all the events have triggered");
		
		if (!$event->getUrlGroup())
			throw new Exception("managed events must be part of a group");

		// only keep parameters that differ from the default ones
		$minimal_params = $event->getMinimalParameters($params, $merge_params);

		if ($this->url_groups)
			$clone_groups = $this->url_groups;
		else 
			$clone_groups = array();
		$enc_ev = $this->encodeEvent($control->getFullId(), $event->getType(), $minimal_params);
		if (is_string($enc_ev))
		{
			$d_len = strlen($enc_ev);
			if ($d_len > 0)
			{
				$last_ch = substr($enc_ev, $d_len - 1);
				if (($last_ch == "~") || ($last_ch == "_") || ($last_ch == "-"))
					$enc_ev = $enc_ev . "-";
			}
		}
		else 
			$enc_ev = $enc_ev[0];
		$clone_groups[$event->getUrlGroup()] = $enc_ev;
		
		$child_groups = self::GetChildUrlGroups($event->getUrlGroup());
		if ($child_groups)
			foreach ($child_groups as $grp)
				if (isset($clone_groups[$grp]))
					unset($clone_groups[$grp]);
					
		if ($this->call_stack)
			throw new Exception("Not implemented");

		return $this->getUrlPrefix() . $this->implodeUrl($clone_groups);
	}
	
	private function implodeUrl($arr, $glue = "/")
	{
		$str = "";
		$pos = 0;
		foreach ($arr as $k => $v)
		{
			if (!$v)
				continue;
			if ($pos > 0)
				$str .= $glue;
			$str .= $v;
			$pos++;
		}
		
		return $str;
	}
	
	public function getChildUrlGroups($group)
	{
		$arr = array();
		$this->getChildUrlGroupsRecursive($group, $arr, $this->urlGroupsTree);
		
		if (count($arr) > 0)
			return $arr;
		else 
			return null;
	}
	
	private function getChildUrlGroupsRecursive($group, &$arr, &$ref, $found = false)
	{
		if (!$found)
		{
			if (isset($ref[$group]) && is_array($ref[$group]))
				$this->getChildUrlGroupsRecursive($group, $arr, $ref[$group], true);
			else 
			{
				if ($ref && is_array($ref))
					foreach ($ref as $k => $v)
					{
						if (is_array($ref[$k]))
							$this->getChildUrlGroupsRecursive($group, $arr, $ref[$k], false);
					}
			}
		}
		else 
		{
			// add them all to the array
			if ($ref && is_array($ref))
			{
				foreach ($ref as $k => $v)
				{
					if (is_string($k))
						$arr[] = $k;
					else if ($v && is_string($v))
						$arr[] = $v;
						
					if ($v && is_array($v))
						self::GetChildUrlGroupsRecursive($group, $arr, $ref[$k], true);
				}
			}
		}
	}
	
	public function encodeEvent($sender_id, $event_type, $parameters)
	{
		if (isset($this->alias_table[$sender_id]))
			$sender_id = $this->alias_table[$sender_id];
		if (isset($this->alias_table[$event_type]))
			$event_type = $this->alias_table[$event_type];
		if ($parameters && is_array($parameters))
		{
			$new_params = array();
			foreach ($parameters as $k => $v)
				if (isset($this->alias_table[$k]))
					$new_params[$this->alias_table[$k]] = $v;
				else 
					$new_params[$k] = $v;
			$parameters = $new_params;
		}

		// compress if needed
		if (!$parameters)
			$d = json_encode(array($sender_id, $event_type));
		else 
			$d = json_encode(array($sender_id, $event_type, $parameters));
		$gz = false;
		if (function_exists("gzcompress"))
		{
			$m = call_user_func("gzcompress", $d);
			if (strlen($m) < strlen($d))
			{
				$d = $m;
				$gz = true;
			}
		}
		$d = base64_encode($d);
		$d = str_replace("/", "-", str_replace("+", ".", $d));
		if ($gz) $d .= "_";
		else 	 $d .= "~";
		
		if (strlen($d) > QUrlManager::$MaxGeneratedPathChunkSize)
		{
			$parts = str_split($d, QUrlManager::$MaxGeneratedPathChunkSize);
			$d = implode("/-/", $parts);
		}
		
		// we return as array to know it's a encoded string
		return array($d);
	}
	
	public function decodeEvent($url, $pos = 0, $count = 0)
	{
		$d_len = strlen($url);
		$last_ch = null;
		if ($d_len > 0)
			$last_ch = substr($url, $d_len - 1);
			
		$data = null;

		if (($last_ch == "~") || ($last_ch == "_"))
		{
			// "_" means gz
			$d = str_replace("-", "/", str_replace(".", "+", $url));
			$d = base64_decode($d);
			if (($last_ch == "_") && (function_exists("gzuncompress")))
				$d = call_user_func("gzuncompress", $d);
			$d = json_decode($d, true);
			if (isset($d["s"]))
			{
				$call_stack = $d["s"];
				unset($d["s"]);
			}
			else 
				$data = $d;
		}
		else 
		{
			throw new QWebException404("Custom events should be decoded by the class that created them: `{$url}`");
		}
		
		if ($data && is_array($data))
		{
			$sender_id = $data[0];
			$event_type = $data[1];
			if (isset($data[2]))
				$parameters = $data[2];
			else 
				$parameters = null;
			
			if (isset($this->reverse_alias_table[$sender_id]))
				$sender_id = $this->reverse_alias_table[$sender_id];
			if (isset($this->reverse_alias_table[$event_type]))
				$event_type = $this->reverse_alias_table[$event_type];
			if ($parameters && is_array($parameters))
			{
				$new_params = array();
				foreach ($parameters as $k => $v)
					if (isset($this->reverse_alias_table[$k]))
						$new_params[$this->reverse_alias_table[$k]] = $v;
					else 
						$new_params[$k] = $v;
				$parameters = $new_params;
			}
			
			return array($sender_id, $event_type, $parameters);
		}
		else 
			throw new QWebException404("Can not decode `{$url}`");
	}
	
	public function encodeCallStack($call_stack)
	{
		if (!$call_stack)
			return null;

		/*
		$stack = array();
		foreach ($call_stack as $cs)
			if (isset($this->alias_table[$cs]))
				$stack[] = $this->alias_table[$cs] . "=";
			else 
				$stack[] = $cs . "=";

		return implode("/", $stack);
		*/
		throw new Exception("Not implemented");
	}
	
	public function decodeCallStack($url_parts)
	{
		throw new Exception("Not implemented");
	}
	
	/**
	 * This function should only be called after all events have been fired
	 *
	 * @return string
	 */
	public function getHash()
	{
		if ($this->call_stack)
			throw new Exception("Not implemented");

		// order them before 
		return $this->implodeUrl($this->url_groups);
	}
	
	public function getDecodePartsCount()
	{
		if (isset($this->decode_parts))
			return count($this->decode_parts);
		else 
			return 0;
	}
	
	public function getDecodePart($index)
	{
		if (isset($this->decode_parts) && isset($this->decode_parts[$index]))
			return $this->decode_parts[$index];
		else 
			return null;
	}
	
	public function getDecodeParts()
	{
		return $this->decode_parts;
	}
	
	public function updateCallStackAndEventsFromUrl($url, $update_hash = true)
	{
		$this->load_url_groups = isset($_POST["__wb_events"]);
		
		$this->no_update_hash = !$update_hash;
		
		if (!$url)
			return;
			
		$url = trim($url, "\\/ ");
		// "/-/" must be removed 
		$url = str_replace("/-/", "", $url);
		
		$call_stack = null;
		$events = array();
			
		$parts = explode("/", $url);
		
		$this->decode_parts = array();
		$temp_parts = array();
		$ppos = 0;
		foreach ($parts as $data)
		{
			$d_len = strlen($data);
			$last_ch = null;
			if ($d_len > 0)
				$last_ch = substr($data, $d_len - 1);

			if (($last_ch == "~") || ($last_ch == "_"))
			{
				$temp_parts[] = $data;
			}
			else 
			{
				if ($last_ch == "-")
					$d = substr($data, 0, $d_len - 1);
				else 
					$d = $data;
				
				$temp_parts[] = $d;
			}
		}
		
		$i = 0;
		$tmp_count = count($temp_parts);
		foreach ($temp_parts as $tmp)
		{
			if (strlen($tmp) > 0)
			{
				$ev = $this->decodeEvent($tmp, $i, $tmp_count);
				// sometimes decode may decide not set an event
				if ($ev)
					$events[] = $ev;
				$i++;
			}
		}
		
		$this->decode_parts = $temp_parts;
		
		$c_events = count($events);
		if ($c_events > 0)
		{
			$post_events = array();
			for ($i = 0; $i < $c_events; $i++)
			{
				if (isset($events[$i][2]))
					$post_events[] = array("id" => $events[$i][0], "type" => $events[$i][1], "params" => $events[$i][2]);
				else 
					$post_events[] = array("id" => $events[$i][0], "type" => $events[$i][1], "params" => null);
			}
			
			$_POST["__wb_events"] = json_encode($post_events);
		}
	}
	
	public function setUrlGroupsTree($tree)
	{
		$this->urlGroupsTree = $tree;
		// set url_groups
		$this->url_groups = array();
		$this->initUrlGroups($this->urlGroupsTree);
	}
	
	private function initUrlGroups(&$arr)
	{
		foreach ($arr as $k => $v)
		{
			if (is_string($k))
				$this->url_groups[$k] = null;
			if (is_string($v))
				$this->url_groups[$v] = null;
			else if (is_array($v))
				$this->initUrlGroups($v);
		}
	}
	
	public function getUrlGroupsTree()
	{
		return $this->urlGroupsTree;
	}
	
	public function getAliasFor($key)
	{
		if (isset($this->alias_table) && isset($this->alias_table[$key]))
			return $this->alias_table[$key];
		else 
			return null;
	}
	
	public function getReverseAliasFor($alias)
	{
		if (isset($this->reverse_alias_table) && isset($this->reverse_alias_table[$alias]))
			return $this->reverse_alias_table[$alias];
		else 
			return null;
	}
	
	public function setAlias($key, $alias)
	{
		$this->alias_table[$key] = $alias;
		$this->reverse_alias_table[$alias] = $key;
	}
	
	public function setAliases($aliases)
	{
		foreach ($aliases as $key => $alias)
		{
			$this->alias_table[$key] = $alias;
			$this->reverse_alias_table[$alias] = $key;
		}
	}
	
	public function unsetAlias($key)
	{
		if (isset($this->alias_table[$key]))
		{
			$alias = $this->alias_table[$key];
			unset($this->alias_table[$key]);
			if (isset($this->reverse_alias_table[$alias]))
				unset($this->reverse_alias_table[$alias]);
		}
	}
	
	public function makeUrl($string, $max_length = null, $replacement = "-")
	{
		$string = ereg_replace("[^[:alpha:]&^[:alnum:]]|[&]|[\^]", $replacement, $string);
		// $s = trim($s, $replacement);
		if ($max_length != null)
			$string = substr($string, 0, $length);
		return $string;
	}
	/*
	private function encodeAndCompressRawEvents($data)
	{
		$d = json_encode($data);
		$gz = false;
		if (function_exists("gzcompress"))
		{
			$m = call_user_func("gzcompress", $d);
			if (strlen($m) < strlen($d))
			{
				$d = $m;
				$gz = true;
			}
		}
		$d = base64_encode($d);
		$d = str_replace("/", "-", str_replace("+", ".", $d));
		if ($gz) $d .= "_";
		else 	 $d .= "~";
		
		if (strlen($d) > QUrlManager::$MaxGeneratedPathChunkSize)
		{
			$parts = str_split($d, QUrlManager::$MaxGeneratedPathChunkSize);
			$d = implode("/-/", $parts);
		}
		
		return $d;
	}
	*/
	
}


?>